﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using KS.DataWrapper.Interface;

namespace KS.DataWrapper.DataWrapper
{
    public partial class DataCacheObject<T> where T : class, IDataClass
    {
        #region Laod from File or Server
        
        /// <summary>
        /// Заполнить коллекцию данными
        /// </summary>
        public async Task LoadCollections()
        {
            if (!this.useLocalCache)
            {
                return;
            }
            
            try
            {
                if (this.LoadFromFile())
                {
                    await this.UpdateCollectionFromServer();
                }
                else
                {
                    HttpResponseMessage response = await this.httpClient.GetAsync(string.Format("{0}/GetAll", this.GetRequestUrl()));
                    response.EnsureSuccessStatusCode(); // Throw on error code.
                    var positionList = await response.Content.ReadAsAsync<IEnumerable<T>>();
                    Parallel.ForEach(positionList, item =>
                    {
                        this.AddToNativeCollection(item); 
                    });
                }
                
                this.SaveToFile();
            }
            catch (Exception)
            {
            }
        }
        
        /// <summary>
        /// Обновление коллекции с сервера
        /// </summary>
        private async Task UpdateCollectionFromServer()
        {
            var sendList = this.nativeCollection.Select(s => new { ID = s.Key, Vts = s.Value.Vts }).ToList();
            HttpResponseMessage response = await this.httpClient.PostAsJsonAsync(string.Format("{0}/SyncCache", this.GetRequestUrl()), sendList);
            
            response.EnsureSuccessStatusCode(); // Throw on error code.
            var serverList = await response.Content.ReadAsAsync<IEnumerable<T>>(); 
            
            foreach (T serverItem in serverList)
            {
                if (serverItem.Vts.Year == 1)
                {
                    //удаляем если дата указывает на это
                    this.RemoveFromNativeCollection(nativeCollection[serverItem.ID]);
                }
                else
                {
                    this.AddToNativeCollection(serverItem);
                }
            }
        }
        
        #endregion
        
        #region Работа с элементами
        
        /// <summary>
        /// Добавить новый элемент в коллекцию
        /// </summary>
        /// <param name="item">Новый элемент коллекции</param>
        private async Task<T> CreateItem(T item)
        {
            try
            {
                if (item == null)
                {
                    return null;
                }
                
                //Type type = (item as dynamic).ID.GetType();
                //dynamic value = Activator.CreateInstance(type);
                //(item as dynamic).ID = value;
                //item.ID = ObjectId.Empty.ToString();
                HttpResponseMessage response = await this.httpClient.PostAsJsonAsync(string.Format("{0}/Add", this.GetRequestUrl()), item);
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                item = await response.Content.ReadAsAsync<T>();
                
                await this.SignalRCollectionAddNotify(item);
                
                return item;
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return null;
            }
        }
        
        private async Task<bool> ContainsItem(T item)
        {
            try
            {
                if (item == null)
                {
                    return false;
                }
                
                //Type type = (item as dynamic).ID.GetType();
                //dynamic value = Activator.CreateInstance(type);
                //(item as dynamic).ID = value;
                //item.ID = ObjectId.Empty.ToString();
                HttpResponseMessage response = await this.httpClient.PostAsJsonAsync(string.Format("{0}/Contains", this.GetRequestUrl()), item);
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                item = await response.Content.ReadAsAsync<T>();
                
                await this.SignalRCollectionAddNotify(item);
                
                return true;
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return false;
            }
        }
        
        /// <summary>
        /// Добавить новый элемент в коллекцию
        /// </summary>
        /// <param name="item">Новый элемент коллекции</param>
        private async Task<IEnumerable<T>> CreateRangeItem(IEnumerable<T> item)
        {
            try
            {
                if (item == null)
                {
                    return null;
                }
                
                //Type type = (item as dynamic).ID.GetType();
                //dynamic value = Activator.CreateInstance(type);
                //(item as dynamic).ID = value;
                //item.ID = ObjectId.Empty.ToString();
                HttpResponseMessage response = await this.httpClient.PostAsJsonAsync(string.Format("{0}/AddRange", this.GetRequestUrl()), item);
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                item = await response.Content.ReadAsAsync<IEnumerable<T>>();
                
                await this.SignalRCollectionAddNotify(item);
                
                return item;
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return null;
            }
        }
        
        /// <summary>
        /// Удалить элемент из коллекции
        /// </summary>
        /// <param name="item">Элемент который нужно удалить из коллекции</param>
        private async Task<bool> DeleteItem(T item)
        {
            try
            {
                if (item == null)
                {
                    return false;
                }
                HttpResponseMessage response = await this.httpClient.DeleteAsync(string.Format("{0}/Delete/{1}", this.GetRequestUrl(), item.ID));
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                
                await this.SignalRCollectionRemoveNotify(item);
                
                return true;
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return false;
            }
        }
        
        /// <summary>
        /// Обновить элемент в коллекции
        /// </summary>
        /// <param name="item">Обновленный элемент</param>
        private async Task<T> UpdateItem(T item)
        {
            try
            {
                if (item == null)
                {
                    return null;
                }
                string url = string.Format("{0}/Update", this.GetRequestUrl());
                HttpResponseMessage response = await this.httpClient.PostAsJsonAsync(url, item);
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                
                item = await response.Content.ReadAsAsync<T>();
                
                await this.SignalRCollectionUpdateNotify(item);
                
                return item;
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return null;
            }
        }
        
        /// <summary>
        /// Получить элемент коллекции с сервера по его ID
        /// </summary>
        /// <param name="id">ID элемента</param>
        /// <returns>Полученный элкмент</returns>
        private async Task<T> GetItemById(dynamic id)
        {
            try
            {
                HttpResponseMessage response = await this.httpClient.GetAsync(string.Format("{0}/Get/{1}", this.GetRequestUrl(), id));
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                return await response.Content.ReadAsAsync<T>();
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return null;
            }
        }
        
        /// <summary>
        /// Получить элемент коллекции с сервера по его ID, синхронная версия
        /// </summary>
        /// <param name="id">ID элемента</param>
        /// <returns>Полученный элкмент</returns>
        private T GetItemByIdSync(dynamic id)
        {
            try
            {
                Task<T> task = Task.Run(() => this.GetItemById(id) as Task<T>);
                return task.Result;
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return null;
            }
        }
        
        /// <summary>
        /// Получить колличество элеменов в коллекции
        /// </summary>
        /// <returns>Колличество элементов</returns>
        private async Task<long> ServerCollectionCount()
        {
            try
            {
                HttpResponseMessage response = await this.httpClient.GetAsync(string.Format("{0}/Count", this.GetRequestUrl()));
                response.EnsureSuccessStatusCode(); // Throw on error code. 
                return await response.Content.ReadAsAsync<long>();
            }
            catch (Exception ex)
            {
                #if DEBUG
                throw ex;
                #endif
                return -1;
            }
        }
        
        #endregion
        
        #region Native Collections
        
        private T AddToNativeCollection(T item)
        {
            if (item == null)
            {
                return null;
            }
            var result = this.nativeCollection.AddOrUpdate(item.ID, item, (key, old) => item);
            try
            {
                this.OnPropertyChanged(() => this.Count);
                this.OnPropertyChanged(() => this.Keys);
                this.OnPropertyChanged(() => this.ObservableCollection);
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, result)); 
            }
            catch
            {
            }
            
            return result;
        }
        
        private T UpdateNativeCollection(T item)
        {
            if (item == null)
            {
                return null;
            }
            var oldItem = this.nativeCollection[item.ID];
            var result = this.nativeCollection.AddOrUpdate(item.ID, item, (key, old) => item);
            
            try
            {
                this.OnPropertyChanged(() => this.Count);
                this.OnPropertyChanged(() => this.Keys);
                this.OnPropertyChanged(() => this.ObservableCollection);
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, result, oldItem));
            }
            catch
            {
            }
            //OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem));
            //OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, result));
            
            return result;
        }
        
        private bool RemoveFromNativeCollection(T item)
        {
            if (item == null)
            {
                return false;
            }
            T old;
            var result = this.nativeCollection.TryRemove(item.ID, out old);
            if (result)
            {
                try
                {
                    this.OnPropertyChanged(() => this.Count);
                    this.OnPropertyChanged(() => this.Keys);
                    this.OnPropertyChanged(() => this.ObservableCollection);
                    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old));
                }
                catch
                {
                }
            }
            return result;
        }
        
        private bool RemoveFromNativeCollectionById(dynamic id)
        {
            return this.RemoveFromNativeCollection(nativeCollection[id]);
        }
        
        private T GetElementNativeCollection(dynamic key)
        {
            return this.nativeCollection[key];
        }
        
        private void SetElementNativeCollection(dynamic key, T value)
        {
            var old = this.nativeCollection[key];
            var newValue = this.UpdateNativeCollection(value);
            try
            {
                this.OnPropertyChanged(() => this.Count);
                this.OnPropertyChanged(() => this.Keys);
                this.OnPropertyChanged(() => this.ObservableCollection);
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue, old));
            }
            catch
            {
            }
        }
        
        private void ClearNativeCollection()
        {
            this.nativeCollection.Clear();
            try
            {
                this.OnPropertyChanged(() => this.Count);
                this.OnPropertyChanged(() => this.Keys);
                this.OnPropertyChanged(() => this.ObservableCollection);
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
            catch
            {
            }
        }
    
        #endregion
    }
}